Skip to content

feat(integrations): add oauth and hidden field types#4639

Merged
miguelpeixe merged 9 commits into
trunkfrom
feat/integration-oauth-field-type
May 22, 2026
Merged

feat(integrations): add oauth and hidden field types#4639
miguelpeixe merged 9 commits into
trunkfrom
feat/integration-oauth-field-type

Conversation

@miguelpeixe

@miguelpeixe miguelpeixe commented Apr 7, 2026

Copy link
Copy Markdown
Member

All Submissions:

Changes proposed in this Pull Request:

Adds two new settings field types for reader activation integrations so integrations can expose OAuth-based connections and programmatically-managed values in the Audience integrations UI.

  • oauth field type: renders a Connect/Disconnect UI in settings-field.js. When no value is stored it shows a primary Connect button linking to field.oauth_url (disabled when no URL is provided). When connected it shows the stored value plus a destructive Disconnect button linking to field.disconnect_url (only rendered when that URL is present).
  • hidden field type: renders nothing in the UI, for values that are managed programmatically rather than edited by the user.
  • Server-side sanitization (class-integration.php): both oauth and hidden values pass through sanitize_settings_field_value() unchanged, since they are read-only or managed programmatically.

Also includes a small defensive fix wrapping rehydrateItem() in the reader-activation store in a try/catch so a failing merge strategy logs a warning instead of throwing.

How to test the changes in this Pull Request:

  1. Register an integration that exposes a settings field with type => 'oauth' and an oauth_url.
  2. Open the Audience → Integrations settings and confirm a Connect button renders linking to the OAuth URL (and is disabled if no URL is set).
  3. With a stored value present, confirm the value and a Disconnect button render (only when disconnect_url is set).
  4. Add a field with type => 'hidden' and confirm it renders nothing while its value is still persisted on save.
  5. Confirm oauth and hidden values are returned unchanged through sanitize_settings_field_value() on save.

Other information:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your changes, as applicable?
  • Have you successfully ran tests with your changes locally?

@miguelpeixe miguelpeixe force-pushed the feat/integration-oauth-field-type branch from f00a8a9 to 33bb748 Compare May 19, 2026 21:08
@miguelpeixe miguelpeixe requested a review from Copilot May 19, 2026 21:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds two new Reader Activation integration settings field types (oauth and hidden) to the Audience → Integrations UI, plus a defensive guard in the reader-activation client store to prevent merge-strategy errors from throwing during rehydration.

Changes:

  • Add oauth and hidden field type handling in the integrations settings UI (connect/disconnect buttons, and non-rendered hidden fields).
  • Add a try/catch wrapper around rehydrateItem() merge logic and log a warning on failure.
  • Allow oauth/hidden settings values to bypass sanitization in Integration::sanitize_settings_field_value().

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
src/wizards/audience/views/integrations/settings-field.js Adds UI rendering for oauth and hidden integration settings field types.
src/reader-activation/store.js Wraps rehydration merge strategy execution in try/catch and logs on errors.
includes/reader-activation/integrations/class-integration.php Adds hidden/oauth cases to settings-value sanitization behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/wizards/audience/views/integrations/settings-field.js Outdated
Comment thread src/wizards/audience/views/integrations/settings-field.js
Comment thread src/reader-activation/store.js Outdated
Comment thread includes/reader-activation/integrations/class-integration.php Outdated
Comment thread includes/reader-activation/integrations/class-integration.php Outdated
Address review feedback on the new oauth settings field:
- Surface help_url alongside description by reusing the shared help
  fragment, matching other field types.
- Avoid passing an empty href to the Connect button when no oauth_url
  is configured, so the disabled button no longer navigates anywhere
  if clicked.
…e throws

Address review feedback on the rehydrateItem error handler:
- Fix the warning message typo ("rehydrated" -> "rehydrate").
- Overwrite the key with the server value when a merge strategy throws,
  matching the no-merge branch so a single bad merge strategy can't
  leave the store with the key untouched.
- Add a test that registers a throwing merge strategy and asserts the
  store falls back to the server value without throwing.
Address review feedback on sanitize_settings_field_value():
- Reject non-scalar payloads for the new oauth and hidden field types
  (return field default or empty string) so REST settings can't store
  arrays/objects where a scalar string is expected.
- Sanitize scalar strings through sanitize_text_field, matching how
  text and password fields are handled.
- Add PHPUnit coverage for the new types via a public test wrapper
  on Sample_Integration, covering scalar sanitization and non-scalar
  rejection with and without an explicit default.
@miguelpeixe miguelpeixe marked this pull request as ready for review May 20, 2026 00:11
@miguelpeixe miguelpeixe requested a review from a team as a code owner May 20, 2026 00:11
@miguelpeixe miguelpeixe added the [Status] Needs Review The issue or pull request needs to be reviewed label May 20, 2026
@adekbadek adekbadek self-assigned this May 20, 2026

@adekbadek adekbadek left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified working in-browser. Suggestions inline.

$type = $field['type'] ?? 'text';
switch ( $type ) {
case 'hidden':
case 'oauth':

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: oauth/hidden values are documented as managed but stay client-writable — The comment frames these as "read-only or managed programmatically," but the sanitizer is reached from the admin settings-save REST path, so an admin client can still POST an arbitrary scalar and overwrite a stored value here. It's capability-gated, so this is a data-integrity nuance rather than a security issue, and there's no live consumer yet. Still, if oauth credentials are meant to be set only by a server-side OAuth callback, consider treating these types as read-only on the write path (ignore inbound writes / return the existing stored value) rather than persisting whatever scalar arrives.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in f12fe78 — relocated the read-only check from the sanitizer to the REST settings save entry point (Integrations::update_integration_settings()). It now drops keys whose field type is in Integration::MANAGED_FIELD_TYPES (oauth, hidden), so an admin POSTing those keys can no longer overwrite stored values. Server-side writers calling Integration::update_settings_field_value() directly are unaffected.

This matters because newspack-manager#565 — the Salesforce NPC integration — is the first live consumer of these field types. It writes access_token, refresh_token, instance_url from a server-side OAuth callback / token-refresh handler / disconnect handler, all via update_settings_field_value. Putting the check inside the sanitizer would have blocked those legitimate writes; gating the REST entry point preserves them while still defending against admin-client overwrites. Added a test covering both paths.

if ( ! is_scalar( $value ) ) {
return $field['default'] ?? '';
}
return \sanitize_text_field( (string) $value );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: sanitize_text_field assumes single-line, tag-free values — sanitize_text_field() strips tags and newlines and collapses whitespace, which is correct for the single-line OAuth tokens/JWTs this PR targets. But the case is shared with the generic hidden bucket — if a future hidden field stores a multiline secret (a PEM key, a serialized blob, a refresh token containing <), the value would be silently corrupted with no error. Worth a one-line note on the case that the assumption is "single-line, tag-free scalar" so the next person adding a hidden field doesn't lose data quietly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in f12fe78 — added an inline note on the sanitize_text_field line documenting the "single-line, tag-free scalar" assumption, so the next person adding a hidden field with a multiline payload (PEM, refresh token containing <, etc.) won't lose data silently.

Note: the same commit also moves the admin-write block out to the REST entry point (see the adjacent thread), so sanitize_text_field is no longer reached for these types from the settings REST endpoint at all. The note still applies to any server-side writer that funnels through update_settings_field_value.

@github-actions github-actions Bot added [Status] Approved The pull request has been reviewed and is ready to merge and removed [Status] Needs Review The issue or pull request needs to be reviewed labels May 20, 2026
@miguelpeixe miguelpeixe requested a review from adekbadek May 20, 2026 18:22

@dkoo dkoo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not doing a full code review, just approving to acknowledge the update_option change in 32ac489 as suggested in https://github.com/Automattic/newspack-manager/pull/565

@miguelpeixe miguelpeixe merged commit daafa6b into trunk May 22, 2026
9 checks passed
@miguelpeixe miguelpeixe deleted the feat/integration-oauth-field-type branch May 22, 2026 17:41
@github-actions

Copy link
Copy Markdown

Hey @miguelpeixe, good job getting this PR merged! 🎉

Now, the needs-changelog label has been added to it.

Please check if this PR needs to be included in the "Upcoming Changes" and "Release Notes" doc. If it doesn't, simply remove the label.

If it does, please add an entry to our shared document, with screenshots and testing instructions if applicable, then remove the label.

Thank you! ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Status] Approved The pull request has been reviewed and is ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants